package com.lucky.aop;

import com.lucky.annotation.Cache;
import com.lucky.annotation.CacheEvict;
import com.lucky.config.CacheConfig;
import com.lucky.util.RedisCache;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

/**
 * @Description 缓存切面
 * @Author Jalyson
 * @Version V1.0.0
 * @Since 1.0
 * @Date 2019/6/27 10:56
 */
@Aspect
@Order(value = 1)
@Component
public class CacheAspect {
    /**
     * 日志对象
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(CacheAspect.class);
    /**
     * ExpressionParser
     */
    protected static final ExpressionParser parser = new SpelExpressionParser();

    /**
     * 缓存获取及设置
     *
     * 标注该方法体为环绕通知
     *
     * @param jp 连接点
     * @param cache 本地缓存
     * @return 返回最新缓存对象
     * @throws Throwable 抛出异常
     */
    @Around("within(com.lucky.service..*) && @annotation(cache)")
    public Object getAndWriteCache(final ProceedingJoinPoint jp, final Cache cache) throws Throwable {
        // 本地缓存未打开 直接调用目标方法
        Object retObj = null;
        if (!CacheConfig.getCacheOpen()) {
            retObj = jp.proceed();
        } else {
            // 获取本地缓存，如果没有名字则调用目标方法获取内容并缓存到本地
            try {
                MethodSignature methodSignature = (MethodSignature) jp.getSignature();
                final Method method = methodSignature.getMethod();
                if (Void.TYPE.equals(method.getReturnType())) {
                    retObj = jp.proceed();
                } else {
                    String finalKey = calSpelVal(method, cache.key(), jp.getArgs());
                    retObj = RedisCache.getData(finalKey);
                    if (null == retObj) {
                        // 本地缓存未命中
                        retObj = jp.proceed();
                        if (null != retObj) {
                            RedisCache.cacheData(finalKey, retObj);
                            LOGGER.info("加入缓存:"+finalKey);
                        }
                    }else{
                        LOGGER.info("命中缓存:"+finalKey);
                    }
                }
            } catch (Throwable throwable) {
                LOGGER.error(throwable.getMessage());
                retObj = jp.proceed();
            }
        }
        return retObj;

    }

    /**
     * 缓存清除
     *
     * 标注该方法体为环绕通知
     *
     * @param jp 切面对象
     * @param cacheEvict 注解参数
     */
    @After("within(com.lucky.service..*) && @annotation(cacheEvict)")
    public void removeCache(final JoinPoint jp, final CacheEvict cacheEvict) {
        //  移除本地缓存
        try {
            MethodSignature methodSignature = (MethodSignature) jp.getSignature();
            Method method = methodSignature.getMethod();
            CacheEvict cache = method.getAnnotation(CacheEvict.class);
            String[] keys = cache.key();
            for (String keyExpression : keys) {
                String finalKey = calSpelVal(method, keyExpression, jp.getArgs());
                Boolean remove = RedisCache.remove(finalKey);
                if(remove){
                    LOGGER.info("cache remove success ! key is {}", finalKey);
                }else {
                    LOGGER.info("cache remove fail,not cache! key is {}", finalKey);
                }
            }
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }

    /**
     * 计算spe表达式值
     * @param method method
     * @param expressionStr expressionStr
     * @param arguments arguments
     * @return
     */
    private static String calSpelVal(Method method, String expressionStr, Object[] arguments) {
        Parameter[] parameters = method.getParameters();
        EvaluationContext ctx = new StandardEvaluationContext();
        int i = 0;
        for (Parameter p : parameters) {
            ctx.setVariable(p.getName(), arguments[i]);
        }
        String description = expressionStr;
        Expression expression = parser.parseExpression(expressionStr);
        Object value = expression.getValue(ctx);
        if (value != null) {
            description = value.toString();
        }
        return description;
    }
}
